#!/usr/bin/env python
# -*- coding: UTF-8 -*-

'''
    Network
        构建网络，训练，提供 API
'''

from Connections import *
from Layer import *
from Connection import *


class Network(object):
    def __init__(self, layers):
        '''
        初始化一个全连接的神经网络
        :param layers: 网络的层，描述神经网络每层节点数目，是一个二维数组
        '''
        self.connections = Connections()
        self.layers = []
        layer_count = len(layers)
        node_count = 0
        for i in range(layer_count):
            self.layers.append(Layer(i, layers[i]))

        for layer in range(layer_count - 1):
            '''
             列表生成式，相当于
             connections=[]
             for up_stream_node in self.layers[layer].nodes:
                 for down_stream_node in self.layers[layer + 1].nodes[:-1]]:
                     connections.add[Connection(up_stream_node, down_stream_node)]
            '''
            ## 循环生成层与层节点之间的连接
            connections = [Connection(up_stream_node, down_stream_node)
                           for up_stream_node in self.layers[layer].nodes
                           for down_stream_node in self.layers[layer + 1].nodes[:-1]]
            for conn in connections:
                self.connections.add_connection(conn)
                conn.down_stream_node.append_up_stream_connection(conn)
                conn.up_stream_node.append_down_stream_connection(conn)


    def predict(self, sample):
        '''
        根据输入的样本预测输出值
        sample: 数组，样本的特征，也就是网络的输入向量
        '''
        self.layers[0].set_output(sample)
        for i in range(1, len(self.layers)):
            self.layers[i].calc_output()
        return map(lambda node: node.output, self.layers[-1].nodes[:-1])

    def train_one_sample(self, labels, sample, rate):
        '''
        训练当个样本
        :param label: 样本的标签，期望输出值
        :param sample: 样本
        :param rate: 学习率
        '''
        s = self.predict(sample)
        print s
        print labels
        self.calc_delta(labels)
        self.update_weight(rate)

    def train(self, labels, data_set, rate, itreation):
        '''
            训练神经网络
        :param labels:  数组，训练样本标签。每个元素是一个样本的标签。
        :param data_set: 二维数组，训练样本特征。每个元素是一个样本的特征。
        :param rate: 学习率
        :param itreation: 迭代次数
        '''
        for i in range(itreation):
            for d in range(len(data_set)):
                self.train_one_sample(labels[d], data_set[d], rate)

    def calc_delta(self, label):
        '''
        计算每一个节点的 δ
        :param label: 节点的
        :return:
        '''
        output_nodes = self.layers[-1].nodes
        for i in range(len(label)):
            output_nodes[i].calc_output_layer_delta(label[i])
        for layer in self.layers[-2::-1]:
            for node in layer.nodes:
                node.calc_hidden_layer_delta()

    def update_weight(self, rate):
        '''
        内部函数，更新每个连接权重
        '''
        for layer in self.layers[:-1]:
            for node in layer.nodes:
                for conn in node.down_stream:
                    conn.update_weight(rate)

    def calc_gradient(self):
        '''
        内部函数，计算每个连接的梯度
        '''
        for layer in self.layers[:-1]:
            for node in layer.nodes:
                for conn in node.down_stream:
                    conn.calc_gradient()

    def get_gradient(self, label, sample):
        '''
        获得网络在一个样本下，每个连接上的梯度
        label: 样本标签
        sample: 样本输入
        '''
        self.predict(sample)
        self.calc_delta(label)
        self.calc_gradient()

    def dump(self):
        '''
        打印网络信息
        '''
        for layer in self.layers:
            layer.dump()
